package org.hawk.gwt.ppc.introspection.impl;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;

import junit.framework.TestCase;

import org.hawk.gwt.ppc.ProcessingUnitsManager;
import org.hawk.gwt.ppc.introspection.IntrospectionUtils;
import org.hawk.gwt.ppc.introspection.JavaGenericArrayTypeScope;
import org.hawk.gwt.ppc.introspection.JavaMethod;
import org.hawk.gwt.ppc.introspection.JavaParameterizedGenericTypeScope;
import org.hawk.gwt.ppc.introspection.JavaScope;
import org.hawk.gwt.ppc.introspection.JavaTypeScope;
import org.hawk.gwt.ppc.introspection.JavaTypeVariable;
import org.hawk.gwt.ppc.introspection.JavaVariable;
import org.hawk.gwt.ppc.introspection.JavaWildcardGenericTypeScope;
import org.hawk.gwt.ppc.introspection.SourcesRepository;
import org.hawk.gwt.ppc.loader.SourcesLoader;
import org.hawk.gwt.ppc.loader.UniversalSourcesLoader;
import org.hawk.gwt.ppc.parser.BlockDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor;
import org.hawk.gwt.ppc.parser.ExpressionDescriptor;
import org.hawk.gwt.ppc.parser.MethodCallDescriptor;
import org.hawk.gwt.ppc.parser.TryCatchFinallyExpressionDescriptor;
import org.hawk.gwt.ppc.utils.PreprocessorException;

public class JavaClassDescriptorTestCase extends TestCase {
	private static final String MOCK1 = "org.hawk.gwt.ppc.scanner.mock.Mock1";
	private static final String MOCK2 = "org.hawk.gwt.ppc.scanner.mock.Mock2";
	private static final String MOCK3 = "org.hawk.gwt.ppc.scanner.mock.Mock3";
	private static final String MOCK3X = "org.hawk.gwt.ppc.reflect.mock.Mock3";
	private static final String MOCK4 = "org.hawk.gwt.ppc.reflect.mock.ArraysMock";
	private static final String MOCK5 = "org.hawk.gwt.ppc.reflect.mock.ComplexMock";
	private static final String MOCK6 = "org.hawk.gwt.ppc.reflect.mock.GenericClassMock";
	
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		ProcessingUnitsManager.getInstance().configure();
	}
	
	public void testVariables() throws PreprocessorException, IOException {
		JavaTypeScope clazz = getClassDescriptor(MOCK1);
		assertVar("VAR", "int", clazz.getScope(21, 3).getVariable("VAR", 21, 3));
		assertVar("zzz", "java.lang.String", clazz.getScope(21, 3).getVariable("zzz", 21, 3));
		assertVar("clazz", "java.lang.Class<?>", clazz.getScope(21, 3).getVariable("clazz", 21, 3));
		assertVar("bitset", "java.util.BitSet", clazz.getScope(21, 3).getVariable("bitset", 21, 3));
		assertVar("inner", "java.lang.String", clazz.getScope(21, 3).getVariable("inner", 21, 3));
		assertVar("str", "java.lang.Class<?>", clazz.getScope(21, 3).getVariable("str", 21, 3));
		assertVar("class", "java.lang.Class", clazz.getScope(21, 3).getVariable("class", 21, 3));
		
		assertVar("class", "java.lang.Class", clazz.getScope(35, 1).getVariable("class", 35, 1));
		assertVar("VAR", "int", clazz.getScope(35, 1).getVariable("VAR", 35, 1));
		assertVar("zzz", "java.lang.String", clazz.getScope(35, 1).getVariable("zzz", 35, 1));
		assertVar("clazz", "java.lang.Class<?>", clazz.getScope(35, 1).getVariable("clazz", 35, 1));
		assertVar("bitset", "java.util.BitSet", clazz.getScope(35, 1).getVariable("bitset", 35, 1));
		assertVar("name", "java.lang.String", clazz.getScope(35, 1).getVariable("name", 35, 1));
	}
	
	public void testGetMethods() throws PreprocessorException, IOException {
		JavaTypeScope clazz = getClassDescriptor(MOCK1);
		
		List<JavaMethod> methods = clazz.getMethods();
		assertEquals(4, methods.size());
		
		assertMethod(methods.get(0), "doSomething", "void", false);
		assertMethod(methods.get(1), "getStringClass", "java.lang.Class<String>", false);
		assertMethod(methods.get(2), "getStringClass", "java.lang.Class<String>", false, "java.lang.String");
		assertMethod(methods.get(3), "getStringClass", "java.lang.Class<String>", false, "java.lang.String", "int");
		
		clazz = IntrospectionUtils.getType(Object.class);
		methods = clazz.getMethods();
		assertEquals(4, methods.size());
		
		assertMethod(methods.get(0), "getClass", "java.lang.Class<?>", false);
		assertMethod(methods.get(1), "equals", "boolean", false, Object.class.getName());
		assertMethod(methods.get(2), "toString", "java.lang.String", false);
		assertMethod(methods.get(3), "hashCode", "int", false);
		
		JavaTypeScope clazzGeneric = getClassDescriptor(MOCK6);
		methods = clazzGeneric.getMethods();
		assertMethod(methods.get(0), "get", "java.util.Map<? extends Number,Map<? super Number,Class<Integer[]>[]>>", 
				false, "int");
		assertMethod(methods.get(1), "size", "int", false);
		assertMethod(methods.get(2), "getT", "T", false, 
				"X1", "X2", "T");
	}
	
	private void assertMethod(JavaMethod method, String name, String returnType, boolean isVarArgs, String...params) {
		assertEquals(name, method.getName());
		assertEquals(returnType, method.getGenericReturnType().toString());
		assertEquals(isVarArgs, method.isVarArgs());
		assertEquals(params.length, method.getGenericParameterTypes().size());
		int i = 0;
		for (JavaTypeScope type : method.getGenericParameterTypes()) {
			assertEquals(params[i++], type.toString());
		}
	}
	
	public void testSimpleExpressions() throws IOException, PreprocessorException {
		SourcesLoader loader = new UniversalSourcesLoader();
		SourcesRepository repository = IntrospectionUtils.getRepository(loader);
		ClassDescriptor clazz = repository.findClass(MOCK2);
		BlockDescriptor block = clazz.getInitializers().get(0);
		BlockDescriptor list = ((TryCatchFinallyExpressionDescriptor)block.get(0)).getTryBlock();
		
		assertEquals(String.class.getName(), 
			IntrospectionUtils.evaluateReturnTypeOfExpression(
					(ExpressionDescriptor) list.get(0), repository).toString());
		assertEquals(Class.class.getName(), 
				IntrospectionUtils.evaluateReturnTypeOfExpression(
						(ExpressionDescriptor) list.get(1), repository).toString());
		assertEquals(byte[].class.getCanonicalName(), 
				IntrospectionUtils.evaluateReturnTypeOfExpression(
						(ExpressionDescriptor) list.get(2), repository).toString());
		assertEquals(Method.class.getName(), 
				IntrospectionUtils.evaluateReturnTypeOfExpression(
						(ExpressionDescriptor) list.get(3), repository).toString());
		assertEquals(Class.class.getName(), 
				IntrospectionUtils.evaluateReturnTypeOfExpression(
						(ExpressionDescriptor) list.get(4), repository).toString());
		assertEquals(Class.class.getName(), 
				IntrospectionUtils.evaluateReturnTypeOfExpression(
						(ExpressionDescriptor) list.get(5), repository).toString());
	}
	
	public void testComplexExpressions() throws PreprocessorException, IOException {
		SourcesLoader loader = new UniversalSourcesLoader();
		SourcesRepository repository = IntrospectionUtils.getRepository(loader);
		ClassDescriptor clazz = repository.findClass(MOCK5);
		BlockDescriptor list = clazz.getMethods().get(0).getBody();
		
		MethodCallDescriptor method = (MethodCallDescriptor) list.get(0);
		assertEquals(int.class.getName(), 
			IntrospectionUtils.evaluateReturnTypeOfExpression((ExpressionDescriptor) method.getArguments().get(0), repository).getName());
		
		method = (MethodCallDescriptor) list.get(2);
		method = (MethodCallDescriptor) method.getTarget();
		
		assertEquals(Object.class.getName(), IntrospectionUtils
				.evaluateReturnTypeOfExpression(method, repository).getName());
	}
	
	public void testArrayTypes() throws PreprocessorException, IOException {
		JavaTypeScope clazz = getClassDescriptor(MOCK4);
		JavaVariable var = clazz.getField("FIELD");
		assertEquals("FIELD", var.getName());
		assertEquals("java.lang.String[]", var.getTypeName());
		JavaMethod method = clazz.getBestMatchMethod("getMultiArray", Collections.<JavaTypeScope>emptyList());
		assertEquals("getMultiArray", method.getName());
		assertEquals("java.lang.String[][][][]", method.getReturnType().toString());
	}
	
	public void testAnonimousClass() throws PreprocessorException, IOException {
		JavaTypeScope clazz = getClassDescriptor(MOCK3);
		assertFalse(clazz.getChildren().isEmpty());
		JavaMethod m = (JavaMethod) clazz.getChildren().get(0);
		assertNotNull(m);
		assertFalse(m.getChildren().isEmpty());
		assertEquals("void",m.getReturnType().getName());
		JavaScope anonimousClass = m.getChildren().get(0);
		assertNotNull(anonimousClass);
		assertTrue(anonimousClass instanceof JavaTypeScope);
		assertEquals(2,anonimousClass.getChildren().size());
		assertTrue(anonimousClass.getChildren().get(0) instanceof JavaMethod);
		JavaTypeScope type = (JavaTypeScope)anonimousClass;
		assertEquals("", type.getName());
		assertEquals(Object.class.getName(), type.getSuperClass().getName());
		assertNotNull(type.getInterfaces());
		assertEquals(1, type.getInterfaces().size());
		assertEquals("com.google.gwt.event.dom.client.ClickHandler",
				type.getInterfaces().get(0).getName());
	}
	
	public void testGenericTypeParameters() throws IOException, PreprocessorException {
		JavaTypeScope clazz = getClassDescriptor(MOCK6);
		List<JavaTypeVariable> result = clazz.getTypeVariables();
		assertEquals(3, result.size());
		assertEquals("X1", result.get(0).getName());
		assertEquals("X2", result.get(1).getName());
		assertEquals("X3", result.get(2).getName());
		
		assertEquals(2, result.get(0).getBounds().size());
		assertEquals(0, result.get(1).getBounds().size());
		assertEquals(0, result.get(2).getBounds().size());
		
		assertEquals(CharSequence.class.getName(), result.get(0).getBounds().get(0).getName());
		assertEquals(RandomAccess.class.getName(), result.get(0).getBounds().get(1).getName());
	}
	
	public void testGetGenericSuperclass() throws IOException, PreprocessorException {
		JavaTypeScope classDescriptor5 = getClassDescriptor(MOCK5);
		assertEquals(classDescriptor5.getSuperClass(), classDescriptor5.getGenericSuperclass());
		
		JavaTypeScope classDescriptor3x = getClassDescriptor(MOCK3X);
		assertEquals(classDescriptor3x.getSuperClass(), classDescriptor3x.getGenericSuperclass());
		
		JavaTypeScope clazz = getClassDescriptor(MOCK6);

		JavaTypeScope superclass = clazz.getGenericSuperclass();
		assertNotNull(superclass);
		assertTrue(superclass instanceof JavaParameterizedGenericTypeScope);
		JavaParameterizedGenericTypeScope pt = (JavaParameterizedGenericTypeScope)superclass;
		assertEquals(1, pt.getTypeParameters().size());
		assertEquals("java.util.AbstractList", pt.getName());
		
		JavaTypeScope param = pt.getTypeParameters().get(0);
		assertNotNull(param);
		assertEquals("java.util.Map", param.getName());
		assertTrue(param instanceof JavaParameterizedGenericTypeScope);
		
		pt = (JavaParameterizedGenericTypeScope) param;
		assertEquals(2, pt.getTypeParameters().size());
		
		JavaTypeScope p1 = pt.getTypeParameters().get(0);
		assertNotNull(p1);
		assertTrue(p1 instanceof JavaWildcardGenericTypeScope);
		JavaWildcardGenericTypeScope p1casted = (JavaWildcardGenericTypeScope)p1; 
		assertEquals(1, p1casted.getUpperBounds().size());
		assertEquals("java.lang.Number", p1casted.getUpperBounds().get(0).getName());
		assertNull(p1casted.getLowerBound());
		
		JavaTypeScope p2 = pt.getTypeParameters().get(1);
		assertNotNull(p2);
		assertEquals("java.util.Map", p2.getName());
		assertTrue(param instanceof JavaParameterizedGenericTypeScope);
		pt = (JavaParameterizedGenericTypeScope) p2;
		assertEquals(2, pt.getTypeParameters().size());
		
		p1 = pt.getTypeParameters().get(0);
		assertNotNull(p1);
		assertTrue(p1 instanceof JavaWildcardGenericTypeScope);
		p1casted = (JavaWildcardGenericTypeScope)p1; 
		assertEquals(1, p1casted.getUpperBounds().size());
		assertEquals("java.lang.Object", p1casted.getUpperBounds().get(0).getName());
		assertEquals("java.lang.Number", p1casted.getLowerBound().getName());
		
		p2 = pt.getTypeParameters().get(1);
		assertNotNull(p2);
		assertTrue(p2 instanceof JavaGenericArrayTypeScope);
		JavaGenericArrayTypeScope array = (JavaGenericArrayTypeScope) p2;
		assertEquals("java.lang.Class", array.getComponentType().getName());
		assertTrue(array.getComponentType() instanceof JavaParameterizedGenericTypeScope);
		
		JavaParameterizedGenericTypeScope component = (JavaParameterizedGenericTypeScope)array.getComponentType();
		assertEquals(1, component.getTypeParameters().size());
		assertEquals("java.lang.Integer[]", component.getTypeParameters().get(0).getName());
	}
	
	public void testGetGenericInterfaces() throws IOException, PreprocessorException {
		JavaTypeScope classDescriptor5 = getClassDescriptor(MOCK5);
		assertEquals(classDescriptor5.getInterfaces(), classDescriptor5.getGenericInterfaces());
		
		JavaTypeScope classDescriptor4 = getClassDescriptor(MOCK4);
		assertEquals(classDescriptor4.getInterfaces(), classDescriptor4.getGenericInterfaces());
		
		JavaTypeScope clazz = getClassDescriptor(MOCK6);
		assertNotNull(clazz.getGenericInterfaces());
		assertEquals(1, clazz.getGenericInterfaces().size());
		JavaTypeScope i1 = clazz.getGenericInterfaces().get(0);
		assertNotNull(i1);
		assertTrue(i1 instanceof JavaParameterizedGenericTypeScope);
		JavaParameterizedGenericTypeScope param = (JavaParameterizedGenericTypeScope)i1;
		assertEquals(2, param.getTypeParameters().size());
		
		assertEquals("org.hawk.gwt.ppc.introspection.impl.BinaryJavaTypeScope", param.getTypeParameters().get(0).getClass().getName());
		assertEquals("java.lang.String", param.getTypeParameters().get(0).getName());
		
		assertEquals("org.hawk.gwt.ppc.introspection.impl.BinaryJavaTypeScope", param.getTypeParameters().get(1).getClass().getName());
		assertEquals("java.lang.Integer", param.getTypeParameters().get(1).getName());
	}
	
	private void assertVar(String name, String type, JavaVariable variable) {
		assertEquals(name, variable.getName());
		assertEquals(type, variable.getTypeName());
	}

	private JavaTypeScope getClassDescriptor(String mock) throws PreprocessorException, IOException {
		SourcesLoader loader = new UniversalSourcesLoader();
		SourcesRepository repository = IntrospectionUtils.getRepository(loader);
		return IntrospectionUtils.resolveType(mock, null, repository);
	}
}
